package com.abel.bigwater.kmgw

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.dataserver.model.PojoHelper
import com.abel.bigwater.dataserver.model.ServiceConfig
import com.abel.bigwater.dataserver.model.qdies.*
import com.abel.bigwater.kmgw.dao.FlowDao
import com.abel.bigwater.kmgw.dao.RtuDao
import com.abel.bigwater.mapper.DataMapper
import com.abel.bigwater.mapper.MeterMapper
import com.abel.bigwater.mapper.RtuMapper
import com.abel.bigwater.model.BwData
import com.abel.bigwater.model.BwRtu
import com.abel.bigwater.model.BwRtuLog
import com.alibaba.fastjson.JSON
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter
import io.netty.channel.socket.DatagramPacket
import org.slf4j.LoggerFactory
import java.util.*

open class QdiesServerHandler(val dataMapper: DataMapper?, val meterMapper: MeterMapper?,
                              val rtuMapper: RtuMapper?, val rtuDao: RtuDao?, val config: ServiceConfig,
                              val kmingDao: FlowDao, val colName: String) : ChannelInboundHandlerAdapter() {

    override fun channelActive(ctx: ChannelHandlerContext?) {
        lgr.info("Got connection from ${ctx?.channel()?.remoteAddress()}")
    }

    override fun channelRead(ctx: ChannelHandlerContext?, msg: Any?) {
        lgr.info("$msg from ${ctx?.channel()?.remoteAddress()}\r\n${JSON.toJSONString(msg, true)}")

        when {
            msg is D10Packet -> {
                handleD10Packet(ctx, msg.also {
                    it.remote = ctx?.channel()?.remoteAddress()?.toString()
                    it.stream = "TCP"
                })
            }

            msg is DatagramPacket -> {
                val hex = BcdString.hexString(msg.content(), withBlank = true)
                lgr.info("got udp packet: ${hex}")
            }

            else -> lgr.warn("other packet type: ${msg?.javaClass}")
        }
    }

    private fun handleD10Packet(ctx: ChannelHandlerContext?, msg: D10Packet) {
        val fd = PojoHelper.toPojo(msg)
        kmingDao.upsert(fd!!, colName)

        if (msg.includeLogin) {
            // download login
            val loginPacket = MainPacket().apply {
                address = msg.address
                func = FuncType.Login
                data = MainLoginData()
                index = msg.index
            }
            val reply = ctx!!.alloc().buffer()
            loginPacket.fill(reply)
            lgr.info("DOWNLOAD to ${msg.address?.bcdString}(${ctx?.channel().remoteAddress()}): ${BcdString.hexString(reply, withBlank = true)}")
            ctx.writeAndFlush(reply)
        }

        // reply back
        when (msg.data) {
            is DtenAskWarnData -> {
                val reply = MainPacket().apply {
                    address = msg.address
                    func = FuncType.ClearWarn
                    data = MainClearWarnData()
                }
                val buf = ctx!!.alloc().buffer().also { reply.fill(it) }
                lgr.info("DOWNLOAD to ${msg.address?.bcdString}(${ctx?.channel().remoteAddress()}): ${BcdString.hexString(buf, withBlank = true)}")
                ctx.writeAndFlush(buf)

                handleWarn(ctx, msg, msg.data as DtenAskWarnData)
            }

            is DtenAskNowData -> {
                // persist.
            }

            is DtenAskPulseHistData -> {
                handleHist(ctx, msg, msg.data as DtenAskPulseHistData)
            }

            is DtenAsk485HistData -> {

            }

            is DtenAskAnalogHistData -> {

            }
        }
    }

    private fun handleWarn(ctx: ChannelHandlerContext?, msg: D10Packet, dtenAskWarnData: DtenAskWarnData) {
        val devId = msg.address!!.bcdString!!
        val _extId = "${config.firmCode}$devId"
        val json = JSON.toJSONString(msg)
        val dev = rtuDao?.fetchRtu(devId, config.firmId ?: "")

        // add rtu log
        val rlog = BwRtuLog().apply {
            logTime = Date()
            remoteServer = "${msg.stream}://${msg.remote}"
            rtuId = devId
            meterId = _extId
            firmId = dev?.firmId ?: config.firmId
            logCmd = "WARN"
            logLen = msg.totalLen!!
            logText = msg.text?.take(200)
            logComment = json.take(40)

            literPulse = 1000
        }
        rtuMapper!!.addRtuLog(rlog)

        // update rtu
        val rtu = BwRtu().apply {
            this.rtuId = devId
            this.firmId = dev?.firmId ?: config.firmId
            this.meterId = _extId

            this.lastTime = Date()
            this.lastCmd = "WARN"
            this.lastText = msg.text!!.take(200)
            this.stateDesc = json.take(40)
            this.lastDataTime = Date()
            this.literPulse = 1000
        }
        if (1 > rtuMapper.updateRtu(rtu)) {
            rtu.also {
                it.firstTime = Date()
                it.firstCmd = "WARN"
                it.firstText = msg.text?.take(40)
            }

            rtuMapper.addRtu(rtu)
        }
    }

    /**
     * 处理历史数据
     */
    private fun handleHist(ctx: ChannelHandlerContext?, msg: D10Packet, data: DtenAskPulseHistData) {
        val devId = msg.address!!.bcdString!!
        val _extId = "${config.firmCode}$devId-${data.internalAddr}"
        val json = JSON.toJSONString(msg)
        val dev = rtuDao?.fetchRtu(devId, config.firmId ?: "")

        // add rtu log
        val rlog = BwRtuLog().apply {
            logTime = Date()
            remoteServer = "${msg.stream}://${msg.remote}"
            rtuId = devId
            meterId = _extId
            firmId = dev?.firmId ?: config.firmId
            logCmd = "HIST"
            logLen = msg.totalLen!!
            logText = msg.text?.take(200)
            logComment = json.take(40)

            literPulse = 1000
            forwardReading = data.readList?.lastOrNull()?.toDouble()
        }
        rtuMapper!!.addRtuLog(rlog)

        // update rtu
        val rtu = BwRtu().apply {
            this.rtuId = devId
            this.firmId = dev?.firmId ?: config.firmId
            this.meterId = _extId

            this.lastTime = Date()
            this.lastCmd = "DATA"
            this.lastText = msg.text!!.take(200)
            this.stateDesc = json.take(40)
            this.lastDataTime = Date()
            this.forwardReading = data.readList?.lastOrNull()?.toDouble()
            this.literPulse = 1000
        }
        if (1 > rtuMapper.updateRtu(rtu)) {
            rtu.also {
                it.firstTime = Date()
                it.firstCmd = "DATA"
                it.firstText = msg.text?.take(40)
            }

            rtuMapper.addRtu(rtu)
        }

        data.readList?.forEachIndexed { index, fl ->
            val bd = BwData().apply {
                extId = _extId
                sampleTime = data.readTimeList!![index]
                literPulse = 1000
                forwardDigits = fl.toDouble()
                firmId = dev?.firmId ?: config.firmId
            }

            if (1 > dataMapper!!.updateRealtimeByValue(bd)) {
                dataMapper.insertRealtime(bd)
            }
        }
    }

    override fun exceptionCaught(ctx: ChannelHandlerContext?, cause: Throwable?) {
        lgr.error("try to reset connection caused by ${cause?.message} from ${ctx?.channel()?.remoteAddress()}", cause)
        ctx?.close()
    }

    companion object {
        private val lgr = LoggerFactory.getLogger(QdiesServerHandler::class.java)
        const val STREAM_TCP = "TCP"
        const val STREAM_UDP = "UDP"
    }

}
